// 《围棋》作者版权所有。版权所有。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package escape

import (
	"cmd/compile/internal/base"
	"cmd/compile/internal/ir"
	"cmd/compile/internal/logopt"
	"cmd/compile/internal/types"
	"fmt"
)

// 下面我们实现了在AST上行走和记录
// 数据流边缘的方法。请注意，因为一个子表达可能有
// 副作用，所以务必访问整个AST。例如，写下：
// 
// if x{
// e.discard（n.Left）
// }else{
// e.value（k，n.Left）
// }
// 
// 或
// 
// if x{
// 
// 不要写：
// 
// /坏：可能在n内失去副作用。Left 
// 如果！x{
// e.value（k，n.Left）
// }

// 一个位置代表一个抽象位置，它存储一个Go 
// 变量。
type location struct {
	n         ir.Node  // 表示的变量或表达式，如果有
	curfn     *ir.Func // 封闭函数
	edges     []edge   // 传入边
	loopDepth int      // 声明处的loopDepth 

	// resultIndex在其函数的结果类型中记录
	// PPARAMOUT变量的元组索引（从1开始）。
	// 对于非PPARAMOUT变量，它是0。
	resultIndex int

	// 在walkOne中使用derefs和walkgen来跟踪来自walk根的最小解引用。
	derefs  int // >=-1 
	walkgen uint32

	// dst和dstEdgeindex在步行时跟踪下一个即时分配
	// /目标位置，以及指向该位置的边的索引
	// 。
	dst        *location
	dstEdgeIdx int

	// queued被walkAll用来跟踪这个位置是否在walk队列中。
	queued bool

	// escapes报告所表示变量的地址是否为
	// escapes；也就是说，变量是否必须是heap 
	// 已分配。
	escapes bool

	// transient报告所表示表达式的
	// 地址是否比该语句寿命长；即
	// 其存储是否可以立即重复使用。
	transient bool

	// 参数C记录所表示参数的泄漏集。
	paramEsc leaks

	captured   bool // 是否捕获了此变量的闭包？
	reassigned bool // 此变量是否已重新分配？
	addrtaken  bool // 此变量的地址是否已被获取？
}

// 边表示两个Go变量之间的赋值边。
type edge struct {
	src    *location
	derefs int // />=-1 
	notes  *note
}

func (l *location) asHole() hole {
	return hole{dst: l}
}

// 泄漏记录参数l泄漏到接收器。
func (l *location) leakTo(sink *location, derefs int) {
	// 如果sink是一个不转义（#44614）的结果参数
	// 我们可以将返回位放入转义分析标签中，
	// 然后记录为结果泄漏。
	if !sink.escapes && sink.isName(ir.PPARAMOUT) && sink.curfn == l.curfn {
		ri := sink.resultIndex - 1
		if ri < numEscResults {
			// 泄漏到结果参数。
			l.paramEsc.AddResult(ri, derefs)
			return
		}
	}

	// 否则，记录为堆泄漏。
	l.paramEsc.AddHeap(derefs)
}

func (l *location) isName(c ir.Class) bool {
	return l.n != nil && l.n.Op() == ir.ONAME && l.n.(*ir.Name).Class == c
}

// 一个洞代表一个Go 
// 表达式的计算上下文。例如，当在“x=**p”中计算p时，我们会有一个洞
// 其中dst==x，derefs==2。
type hole struct {
	dst    *location
	derefs int // >=-1 
	notes  *note

	// /addrtaken表示此上下文是否使用
	// 表达式的地址，与地址是否实际存储到变量中无关。
	addrtaken bool
}

type note struct {
	next  *note
	where ir.Node
	why   string
}

func (k hole) note(where ir.Node, why string) hole {
	if where == nil || why == "" {
		base.Fatalf("note: missing where/why")
	}
	if base.Flag.LowerM >= 2 || logopt.Enabled() {
		k.notes = &note{
			next:  k.notes,
			where: where,
			why:   why,
		}
	}
	return k
}

func (k hole) shift(delta int) hole {
	k.derefs += delta
	if k.derefs < -1 {
		base.Fatalf("derefs underflow: %v", k.derefs)
	}
	k.addrtaken = delta < 0
	return k
}

func (k hole) deref(where ir.Node, why string) hole { return k.shift(1).note(where, why) }
func (k hole) addr(where ir.Node, why string) hole  { return k.shift(-1).note(where, why) }

func (k hole) dotType(t *types.Type, where ir.Node, why string) hole {
	if !t.IsInterface() && !types.IsDirectIface(t) {
		k = k.shift(1)
	}
	return k.note(where, why)
}

func (b *batch) flow(k hole, src *location) {
	if k.addrtaken {
		src.addrtaken = true
	}

	dst := k.dst
	if dst == &b.blankLoc {
		return
	}
	if dst == src && k.derefs >= 0 { // dst=dst，dst=*dst。。。
		return
	}
	if dst.escapes && k.derefs < 0 { // dst=&src 
		if base.Flag.LowerM >= 2 || logopt.Enabled() {
			pos := base.FmtPos(src.n.Pos())
			if base.Flag.LowerM >= 2 {
				fmt.Printf("%s: %v escapes to heap:\n", pos, src.n)
			}
			explanation := b.explainFlow(pos, dst, src, k.derefs, k.notes, []*logopt.LoggedOpt{})
			if logopt.Enabled() {
				var e_curfn *ir.Func // TODO（mdempsky）：修复。
				logopt.LogOpt(src.n.Pos(), "escapes", "escape", ir.FuncName(e_curfn), fmt.Sprintf("%v escapes to heap", src.n), explanation)
			}

		}
		src.escapes = true
		return
	}

	// TODO（mdempsky）：重复数据消除边缘？
	dst.edges = append(dst.edges, edge{src: src, derefs: k.derefs, notes: k.notes})
}

func (b *batch) heapHole() hole    { return b.heapLoc.asHole() }
func (b *batch) discardHole() hole { return b.blankLoc.asHole() }

func (b *batch) oldLoc(n *ir.Name) *location {
	if n.Canonical().Opt == nil {
		base.Fatalf("%v has no location", n)
	}
	return n.Canonical().Opt.(*location)
}

func (e *escape) newLoc(n ir.Node, transient bool) *location {
	if e.curfn == nil {
		base.Fatalf("e.curfn isn't set")
	}
	if n != nil && n.Type() != nil && n.Type().NotInHeap() {
		base.ErrorfAt(n.Pos(), "%v is incomplete (or unallocatable); stack allocation disallowed", n.Type())
	}

	if n != nil && n.Op() == ir.ONAME {
		if canon := n.(*ir.Name).Canonical(); n != canon {
			base.Fatalf("newLoc on non-canonical %v (canonical is %v)", n, canon)
		}
	}
	loc := &location{
		n:         n,
		curfn:     e.curfn,
		loopDepth: e.loopDepth,
		transient: transient,
	}
	e.allLocs = append(e.allLocs, loc)
	if n != nil {
		if n.Op() == ir.ONAME {
			n := n.(*ir.Name)
			if n.Class == ir.PPARAM && n.Curfn == nil {
				// 好；隐藏参数
			} else if n.Curfn != e.curfn {
				base.Fatalf("curfn mismatch: %v != %v for %v", n.Curfn, e.curfn, n)
			}

			if n.Opt != nil {
				base.Fatalf("%v already has a location", n)
			}
			n.Opt = loc
		}
	}
	return loc
}

// teeHole返回一个新的孔，该孔流入ks的每个孔，
// 类似于Unix tee（1）命令。
func (e *escape) teeHole(ks ...hole) hole {
	if len(ks) == 0 {
		return e.discardHole()
	}
	if len(ks) == 1 {
		return ks[0]
	}
	// TODO（mdempsky）：如果只有一个非废弃孔，则进行优化？

	// 给定孔“l1=”、“l2=”、“l3=*”、。。。，创建一个
	// 新的临时位置ltmp，将其连接到位，并返回
	// 一个用于“ltmp=”的孔。注意，“p=&q”和“p=&tmp；tmp=q”在语义上并不等价。为了组合像“l1 
	loc := e.newLoc(nil, true)
	for _, k := range ks {
		// /=”和“l2=&&u”这样的孔，我们需要将它们连接为“l1=
		// /*ltmp”和“l2=ltmp”，并返回“ltmp=&”
		// 。
		if k.derefs < 0 {
			base.Fatalf("teeHole: negative derefs")
		}

		e.flow(k, loc)
	}
	return loc.asHole()
}

// later返回一个流入k的新孔，但一段时间后返回。
// 其主要作用是防止立即重用临时
// 在订购过程中引入的变量。
func (e *escape) later(k hole) hole {
	loc := e.newLoc(nil, false)
	e.flow(k, loc)
	return loc.asHole()
}

// Fmt从节点打印调用，以打印有关逃逸分析结果的信息。
func Fmt(n ir.Node) string {
	text := ""
	switch n.Esc() {
	case ir.EscUnknown:
		break

	case ir.EscHeap:
		text = "esc(h)"

	case ir.EscNone:
		text = "esc(no)"

	case ir.EscNever:
		text = "esc(N)"

	default:
		text = fmt.Sprintf("esc(%d)", n.Esc())
	}

	if n.Op() == ir.ONAME {
		n := n.(*ir.Name)
		if loc, ok := n.Opt.(*location); ok && loc.loopDepth != 0 {
			if text != "" {
				text += " "
			}
			text += fmt.Sprintf("ld(%d)", loc.loopDepth)
		}
	}

	return text
}
